To make code easy to read and maintain, we should follow some best practices.
In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.
No String Literal Property Access
We shouldn’t use string literals to access properties if they’re valid identifiers.
For instance, instead of writing:
obj['prop']
We write:
obj.prop
But we can still use brackets to access properties that aren’t valid identifiers.
For instance, we can still write:
obj['foo-bar'];
No Throwing Strings
We shouldn’t throw strings since we toss out lots of information like the stack trace and the error type.
For instance, instead of writing:
throw 'error';
We write:
throw new Error("error");
No Switch Case Fallthrough
We shouldn’t have switch cases that don’t have a break
or return
statement.
For example, instead of writing:
switch (foo) {
case 1:
doWork(foo);
case 2:
doOtherWork(foo);
}
We write:
switch (foo) {
case 1:
doWork(foo);
break;
case 2:
doOtherWork(foo);
break;
}
No Tautology Expressions
We shouldn’t compare a value with itself.
They always run or don’t run.
For instance, instead of writing:
3 === 3
We write:
val === 3
No Assignment to this
We shouldn’t assign this
to a value.
If we need to do that, we should use arrow functions instead.
So instead of writing:
const self = this;
setTimeout(function() {
self.work();
});
We write:
setTimeout(() => {
this.work();
});
No Unbound Method
We shouldn’t use instance methods outside a method call.
For instance, instead of writing:
class Foo {
public log(): void {
console.log(this);
}
}
Foo.log();
We write:
class Foo {
public log(): void {
console.log(this);
}
}
const foo = new Foo();
foo.log();
We can also write an arrow function and use bind
if we don’t need to reference the class instance:
class Foo {
public log = (): void => {
console.log(foo);
}
}
`const foo = new` Foo`();
foo.`log()`;`
or:
class Foo {
public log() {
console.log(this);
}
}
const foo = new Foo();
const manualLog = foo.log.bind(foo);
manualLog.log();
No Unnecessary Class
We can put lots of things outside of classes.
For instance, we can have top-level functions and variables that aren’t contained in a class.
No Unsafe any Casts
We shouldn’t cast any variable or value to any
.
This means it can contain anything.
If it has dynamic properties, we can use index signatures, union or intersection types, and more.
Instead of casting to any
:
const foo = bar as any;
We can write:
interface Bar {
[key: string]: string | number;
[index: number]: string;
baz: number;
}
const foo: Bar = bar;
We have baz
and other dynamic properties in the Bar
interface.
No Unsafe finally
We shouldn’t have return
, continue
, break
or throws
in finally
blocks since they override the control flow statements of the try
and catch
blocks.
For instance, instead of writing:
try {
doWork();
}
catch(ex) {
console.log(ex);
}
finally {
return false;
}
We write:
try {
doWork();
reutrn true;
}
catch(ex) {
console.log(ex);
return false;
}
No Unused Expression
We shouldn’t have any unused expression statements in our code.
For instance, instead of writing:
a === 1
We remove them or use them in statements.
No Using Variables Before Declaring Them
We shouldn’t use variables declaring them.
This is possible with var
, but throws errors with let
and const
.
Instead of writing:
console.log(x);
var x = 1;
We write:
var x = 1;
console.log(x);
let y = 2;
console.log(y);
const z = 3;
console.log(z);
No var Keyword
We should think twice about using the var
keyword.
Instead of using var
, which has a tricky function scope and hoisting, we use let
and const
which are block-scoped and no tricky scoping issues.
For example, instead of writing:
var x = 2;
We write:
let a = 1;
const bar = 3;
Conclusion
We should access properties with the dot notation whenever possible.
Also, we should use the modern syntax for declaring variables.
Downcasting to any
is also not good since we can assign anything to it.
We should also have break
or return
in our case
blocks.